using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Common.Shared.IO;
using System.Threading;
using System.Drawing;

namespace Common.Shared.Windows.Forms {
  public class FileSystemTreeView : TreeView {
    private SystemImageList imageList = null;
    private DriveDetector _diskDetector = null;

    private string _selectedPath = string.Empty;

    private FolderFileSystemTreeNode _documentsNode = null;
    private bool _showMyDocuments = true;
    private bool _showFiles = true;

    public event TreeViewEventHandler SelectedPathChanged;
    public event TreeViewEventHandler NodeDoubleClick;

    private delegate void AddFileSystemTreeNode ( TreeNodeCollection parent, FileSystemTreeNode fstn );
    private delegate void GenericDelegate ( );


    public FileSystemTreeView ( ) {
      imageList = new SystemImageList ( SystemImageListSize.SmallIcons );
      SystemImageList.SetImageList ( this, imageList, false );
      _diskDetector = new DriveDetector ( this );
      _diskDetector.DeviceArrived += new DriveDetectorEventHandler ( this.DisksStatusChanged );
      _diskDetector.DeviceRemoved += new DriveDetectorEventHandler ( this.DisksStatusChanged );
      _diskDetector.QueryRemove += new DriveDetectorEventHandler ( this.DisksStatusChanged );

      if ( !this.DesignMode ) {
        BuildInitialNodes ( );
      }
      Initialize ( );
    }

    private void Initialize ( ) {
      this.BeforeCollapse += new TreeViewCancelEventHandler ( FileSystemTreeView_BeforeCollapse );
      this.BeforeCheck += new TreeViewCancelEventHandler ( FileSystemTreeView_BeforeCheck );
      this.BeforeExpand += new TreeViewCancelEventHandler ( FileSystemTreeView_BeforeExpand );
      this.BeforeLabelEdit += new NodeLabelEditEventHandler ( FileSystemTreeView_BeforeLabelEdit );
      this.BeforeSelect += new TreeViewCancelEventHandler ( FileSystemTreeView_BeforeSelect );
      this.AfterCheck += new TreeViewEventHandler ( FileSystemTreeView_AfterCheck );
      this.AfterCollapse += new TreeViewEventHandler ( FileSystemTreeView_AfterCollapse );
      this.AfterExpand += new TreeViewEventHandler ( FileSystemTreeView_AfterExpand );
      this.AfterLabelEdit += new NodeLabelEditEventHandler ( FileSystemTreeView_AfterLabelEdit );
      this.AfterSelect += new TreeViewEventHandler ( FileSystemTreeView_AfterSelect );

      this.DoubleClick += new EventHandler ( FileSystemTreeView_DoubleClick );
    }

    protected virtual void OnSelectedPathChanged ( TreeViewEventArgs e ) {
      if ( this.SelectedPathChanged != null )
        this.SelectedPathChanged ( this, e );
    }

    protected virtual void OnNodeDoubleClick ( TreeViewEventArgs e ) {
      if ( this.NodeDoubleClick != null )
        this.NodeDoubleClick ( this, e );
    }

    public bool ShowFiles { get { return this._showFiles; } set { this._showFiles = value; } }
    public bool ShowMyDocuments { get { return this._showMyDocuments; } set { this._showMyDocuments = value; } }

    public string SelectedPath { get { return this._selectedPath; } }
    #region Node Events

    void FileSystemTreeView_DoubleClick ( object sender, EventArgs e ) {
      FileSystemTreeNode fstn = this.GetNodeAt ( this.PointToClient ( MousePosition ) ) as FileSystemTreeNode;
      if ( fstn != null ) {
        OnNodeDoubleClick ( new TreeViewEventArgs ( fstn, TreeViewAction.ByMouse ) );
      }
    }


    void FileSystemTreeView_AfterSelect ( object sender, TreeViewEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnAfterSelect ( e );
        if ( fstn.FileInfo != null )
          this._selectedPath = fstn.FileInfo.FullName;
        else if ( fstn.DirectoryInfo != null )
          this._selectedPath = fstn.DirectoryInfo.FullName;
        else
          this._selectedPath = string.Empty;

        OnSelectedPathChanged ( new TreeViewEventArgs ( e.Node, e.Action ) );
      }
    }

    void FileSystemTreeView_AfterLabelEdit ( object sender, NodeLabelEditEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnAfterLabelEdit ( e );
      }
    }

    void FileSystemTreeView_AfterExpand ( object sender, TreeViewEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnAfterExpand ( e );
      }
    }

    void FileSystemTreeView_AfterCollapse ( object sender, TreeViewEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnAfterCollapse ( e );
      }
    }

    void FileSystemTreeView_AfterCheck ( object sender, TreeViewEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnAfterCheck ( e );
      }
    }

    void FileSystemTreeView_BeforeSelect ( object sender, TreeViewCancelEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnBeforeSelect ( e );
      }
    }

    void FileSystemTreeView_BeforeLabelEdit ( object sender, NodeLabelEditEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnBeforeLabelEdit ( e );
      }
    }

    void FileSystemTreeView_BeforeExpand ( object sender, TreeViewCancelEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        if ( fstn is FolderFileSystemTreeNode ) {
          if ( !IsLoaded ( fstn ) ) {
            if ( this.InvokeRequired )
              this.Invoke ( new GenericDelegate ( fstn.Nodes.Clear ), null );
            else
              fstn.Nodes.Clear ( );
            Thread thread = new Thread ( new ParameterizedThreadStart ( delegate ( object obj ) {
              if ( obj is FileSystemTreeNode )
                LoadDirectoriesForNode ( obj as FileSystemTreeNode );
            } ) );
            thread.Name = string.Format ( "{0}ExpandThread", fstn.Text );
            thread.Start ( fstn );
          }
        }
        fstn.OnBeforeExpand ( e );
      }
    }

    void LoadDirectoriesForNode ( FileSystemTreeNode fstn ) {
      if ( fstn.DirectoryInfo != null & fstn.FileInfo == null ) {
        foreach ( DirectoryInfo dir in fstn.DirectoryInfo.GetDirectories ( ) ) {
          FolderFileSystemTreeNode ffstn = new FolderFileSystemTreeNode ( dir );
          if ( this.InvokeRequired )
            this.Invoke ( new AddFileSystemTreeNode ( AddTreeNode ), new object[] { fstn.Nodes, ffstn } );
          else
            AddTreeNode ( fstn.Nodes, ffstn );
          if ( this.InvokeRequired )
            this.Invoke ( new GenericDelegate ( fstn.Expand ), null );
          else
            fstn.Expand ( );
        }

        if ( this._showFiles ) {
          foreach ( FileInfo file in fstn.DirectoryInfo.GetFiles ( ) ) {
            FileFileSystemTreeNode ffstn = new FileFileSystemTreeNode ( file );
            if ( this.InvokeRequired )
              this.Invoke ( new AddFileSystemTreeNode ( AddTreeNode ), new object[] { fstn.Nodes, ffstn } );
            else
              AddTreeNode ( fstn.Nodes, ffstn );
            if ( this.InvokeRequired )
              this.Invoke ( new GenericDelegate ( fstn.Expand ), null );
            else
              fstn.Expand ( );
          }
        }
      }
    }

    void AddTreeNode ( TreeNodeCollection parent, FileSystemTreeNode fstn ) {
      fstn.SetIcons ( this.imageList );
      parent.Add ( fstn );
    }

    /// <summary>
    /// Determines whether [is not loaded] [the specified FSTN].
    /// </summary>
    /// <param name="fstn">The FSTN.</param>
    /// <returns>
    /// 	<c>true</c> if [is not loaded] [the specified FSTN]; otherwise, <c>false</c>.
    /// </returns>
    bool IsLoaded ( FileSystemTreeNode fstn ) {
      if ( fstn.Nodes.Count == 1 )
        if ( fstn.Nodes[ 0 ].GetType ( ) == typeof ( DummyTreeNode ) )
          return false;
      return true;
    }

    void FileSystemTreeView_BeforeCheck ( object sender, TreeViewCancelEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnBeforeCheck ( e );
      }
    }

    void FileSystemTreeView_BeforeCollapse ( object sender, TreeViewCancelEventArgs e ) {
      if ( e.Node is FileSystemTreeNode ) {
        FileSystemTreeNode fstn = e.Node as FileSystemTreeNode;
        fstn.OnBeforeCollapse ( e );
      }
    }
    #endregion

    protected override void OnHandleCreated ( EventArgs e ) {
      base.OnHandleCreated ( e );
    }

    private void BuildInitialNodes ( ) {
      if ( this.InvokeRequired )
        this.Invoke ( new GenericDelegate ( this.Nodes.Clear ), null );
      else
        this.Nodes.Clear ( );

      Thread thread = new Thread ( new ThreadStart ( this.LoadDriveNodes ) );
      thread.Start ( );
    }

    private void LoadDriveNodes ( ) {
      foreach ( LogicalDriveInfo drive in _diskDetector.Disks ) {
        DriveFileSystemTreeNode dskfstn = new DriveFileSystemTreeNode ( drive );
        if ( this.InvokeRequired )
          this.Invoke ( new AddFileSystemTreeNode ( AddTreeNode ), new object[] { this.Nodes, dskfstn } );
        else
          AddTreeNode ( this.Nodes, dskfstn );
      }
      if ( this._showMyDocuments ) {
        if ( this._documentsNode == null ) {
          this._documentsNode = new FolderFileSystemTreeNode ( new DirectoryInfo ( Environment.GetFolderPath ( Environment.SpecialFolder.MyDocuments ) ) );
        }
        if ( this.InvokeRequired )
          this.Invoke ( new AddFileSystemTreeNode ( AddTreeNode ), new object[] { this.Nodes, _documentsNode } );
        else
          AddTreeNode ( this.Nodes, _documentsNode );
      }
    }

    private void DisksStatusChanged ( object sender, DriveDetectorEventArgs e ) {
      Console.WriteLine ( "Status Changed for {0}", e.Drive.Letter );
      this.BuildInitialNodes ( );
    }


    #region Hidden Properties
    /// <summary>
    /// Gets or sets the delimiter string that the tree node path uses.
    /// </summary>
    /// <value></value>
    /// <returns>The delimiter string that the tree node <see cref="P:System.Windows.Forms.TreeNode.FullPath"></see> property uses. The default is the backslash character (\).</returns>
    protected new string PathSeparator {
      get { return base.PathSeparator; }
      set { base.PathSeparator = value; }
    }
    #endregion

    protected override void WndProc ( ref Message m ) {
      base.WndProc ( ref m );
      if ( _diskDetector != null ) {
        _diskDetector.WndProc ( ref m );
      }
    }
  }
}
